/*
 * Copyright 2014 alexpad@gmail.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package alexpad.karaokarc.midi;

import java.io.*;
import javax.sound.midi.*;

/**
 *
 * @author alexpad@gmail.com
 */
public final class MidiUtil {
	
	public static javax.swing.filechooser.FileFilter swingFileFilterMidiAndFolders = new javax.swing.filechooser.FileFilter(){
		@Override
		public boolean accept(File f) {
			if(f.isDirectory()) return true;
			return hasMidiFileExt(f.getName().toLowerCase());
		}

		@Override
		public String getDescription() {
			return "Midi file (.mid .midi .kar)";
		}
	};
	
	public static java.io.FileFilter midiFileFilter = new java.io.FileFilter(){
		@Override
		public boolean accept(File f) {
			return hasMidiFileExt(f.getName().toLowerCase());
		}
	};
	
	public static final String DEFAULT_MIDI_CHARSET = "CP1252";
	
	public static final int STANDARD_VOICE_CHANNEL = 3;
	
	public static final int META_TEXT_EVENT = 0x01;
	public static final int META_COPYRIGHT_NOTICE = 0x02;
	public static final int META_TRACK_NAME = 0x03;
	public static final int META_INSTRUMENT_NAME = 0x04;
	public static final int META_LYRIC = 0x05;
	public static final int META_MARKER = 0x06;
	public static final int META_CUE_POINT = 0x07;
	public static final int META_END_TRACK = 0x2F;
	
	public static final String KARAOKE_FILE_EXT = ".kar";
	
	public static boolean hasMidiFileExt(String fileName){
		return (fileName.endsWith(".mid") || fileName.endsWith(".midi") || fileName.endsWith(".kar"));
	}
	
	public static boolean hasKaraokeFileExt(String fileName){
		return (fileName.endsWith(".kar"));
	}
	
	public static File[] listMidiFiles(File dir){
		return dir.listFiles(midiFileFilter);
	}
	
	public static void reset(Sequencer sequencer){
		sequencer.setTickPosition(0);
		sequencer.setTempoFactor(1.0F);
		cancelAllSolo(sequencer);
		cancelAllMute(sequencer);
	}
	
	public static void setSolo(Sequencer sequencer, int trackIndex){
		cancelAllSolo(sequencer);
		if(trackIndex < sequencer.getSequence().getTracks().length){
			sequencer.setTrackSolo(trackIndex, true);
		}
	}
	
	public static void cancelAllSolo(Sequencer sequencer){
		for(int n=0;n<sequencer.getSequence().getTracks().length;n++){
			sequencer.setTrackSolo(n, false);
		}
	}
	
	public static void cancelAllMute(Sequencer sequencer){
		for(int n=0;n<sequencer.getSequence().getTracks().length;n++){
			sequencer.setTrackMute(n, false);
		}
	}
	
	public static int countNoteEvents(Track track) {
		MidiEvent midiEvent;
		MidiMessage midiMessage;
		ShortMessage shortMessage;	
		int count = 0;
		for(int n=0;n<track.size();n++){
			midiEvent = track.get(n);
			midiMessage = midiEvent.getMessage();
			if(midiMessage instanceof ShortMessage){
				shortMessage = (ShortMessage) midiMessage;
				if(shortMessage.getCommand()==ShortMessage.NOTE_ON && shortMessage.getData2()>0){
					count++;
				}
			}
		}
		return count;
	}
	
	public static Sequence createSequence(float divisionType, int resolution, Track[] tracks, boolean[] includeTracks) throws InvalidMidiDataException {
		Sequence sequence = new Sequence(divisionType, resolution);
		Track track;
		for(int t=0;t<tracks.length;t++){
			if(includeTracks!=null && !includeTracks[t]) continue;
			track = sequence.createTrack();
			for(int e=0;e<tracks[t].size();e++){
				track.add(tracks[t].get(e));
			}
		}
		return sequence;
	}
	
	public static Sequence cloneSequence(Sequence source) throws Exception{
		Sequence sequence = new Sequence(source.getDivisionType(), source.getResolution());
		Track track;
		Track[] sourceTracks = source.getTracks();
		for(int t=0;t<sourceTracks.length;t++){
			track = sequence.createTrack();
			for(int e=0;e<sourceTracks[t].size();e++){
				track.add(sourceTracks[t].get(e));
			}
		}
		return sequence;
	}
	
	public static Sequence cloneSequenceAsFormat0(Sequence source) throws Exception{
		Sequence sequence = new Sequence(source.getDivisionType(), source.getResolution());
		Track track = sequence.createTrack();
		Track[] sourceTracks = source.getTracks();
		for(int t=0;t<sourceTracks.length;t++){
			for(int e=0;e<sourceTracks[t].size();e++){
				track.add(sourceTracks[t].get(e));
			}
		}
		return sequence;
	}
	
	public static MidiChannelMessages[] getInstruments(Sequence sequence) {
		MidiChannelMessages[] instruments = new MidiChannelMessages[16];
		
		MidiEvent midiEvent;
		MidiMessage midiMessage;
		int status;
		int channel;
		int command;
		for(Track sourceTrack : sequence.getTracks()){
			for(int e=0;e<sourceTrack.size();e++){
				midiEvent = sourceTrack.get(e);
				midiMessage = midiEvent.getMessage();
				status = midiMessage.getStatus();
				channel = status & 0x0F;
				command = status & 0xF0;
				// colleziono tutti i program change per il canale
				if(command==ShortMessage.PROGRAM_CHANGE){
					if(instruments[channel]==null){
						instruments[channel] = new MidiChannelMessages();
					}
					instruments[channel].add(((ShortMessage)midiMessage));
				}
			}
		}
		return instruments;
	}
	
	public static Sequence loadSequence(File file) throws InvalidMidiDataException, IOException{
		Sequence midiFileSequence = MidiSystem.getSequence(file);
		Sequence sequence = new Sequence(midiFileSequence.getDivisionType(), midiFileSequence.getResolution());
		
		Track[] tracks = new Track[16];
		for(int n=0;n<tracks.length;n++){
			tracks[n] = sequence.createTrack();
		}
		MidiEvent midiEvent;
		MidiMessage midiMessage;
		int status;
		for(Track sourceTrack : midiFileSequence.getTracks()){
			for(int e=0;e<sourceTrack.size();e++){
				midiEvent = sourceTrack.get(e);
				midiMessage = midiEvent.getMessage();
				status = midiMessage.getStatus();
				if(status>0xEF){
					// i SYSTEM MESSAGE li colleziono tutti sulla traccia 0
					tracks[0].add(midiEvent);
				}
				else{
					tracks[status & 0x0F].add(midiEvent);
				}
			}
		}
		return sequence;
	}
}
